/*
* Copyright (c) 2003, the JUNG Project and the Regents of the University 
* of California
* All rights reserved.
*
* This software is open-source under the BSD license; see either
* "license.txt" or
* http://jung.sourceforge.net/license.txt for a description.
*/
package edu.uci.ics.jung.algorithms.shortestpath;

import java.util.HashMap;
import java.util.Map;

import edu.uci.ics.jung.graph.Hypergraph;

/**
 * Computes the shortest path distances for graphs whose edges are not weighted
 * (using BFS).
 * 
 * @author Scott White
 */
public class UnweightedShortestPath<V, E>
		implements ShortestPath<V, E>, Distance<V> {
	private Map<V, Map<V, Number>> mDistanceMap;
	private Map<V, Map<V, E>> mIncomingEdgeMap;
	private Hypergraph<V, E> mGraph;
	private Map<V, Number> distances = new HashMap<V, Number>();

	/**
	 * Constructs and initializes algorithm
	 * 
	 * @param g
	 *            the graph
	 */
	public UnweightedShortestPath(Hypergraph<V, E> g) {
		mDistanceMap = new HashMap<V, Map<V, Number>>();
		mIncomingEdgeMap = new HashMap<V, Map<V, E>>();
		mGraph = g;
	}

	/**
	 * @see edu.uci.ics.jung.algorithms.shortestpath.Distance#getDistance(Object,
	 *      Object)
	 */
	@Override
	public Number getDistance(V source, V target) {
		Map<V, Number> sourceSPMap = getDistanceMap(source);
		return sourceSPMap.get(target);
	}

	/**
	 * @see edu.uci.ics.jung.algorithms.shortestpath.Distance#getDistanceMap(Object)
	 */
	@Override
	public Map<V, Number> getDistanceMap(V source) {
		Map<V, Number> sourceSPMap = mDistanceMap.get(source);
		if (sourceSPMap == null) {
			computeShortestPathsFromSource(source);
			sourceSPMap = mDistanceMap.get(source);
		}
		return sourceSPMap;
	}

	/**
	 * @see edu.uci.ics.jung.algorithms.shortestpath.ShortestPath#getIncomingEdgeMap(Object)
	 */
	@Override
	public Map<V, E> getIncomingEdgeMap(V source) {
		Map<V, E> sourceIEMap = mIncomingEdgeMap.get(source);
		if (sourceIEMap == null) {
			computeShortestPathsFromSource(source);
			sourceIEMap = mIncomingEdgeMap.get(source);
		}
		return sourceIEMap;
	}

	/**
	 * Computes the shortest path distances from a given node to all other
	 * nodes.
	 * 
	 * @param source
	 *            the source node
	 */
	private void computeShortestPathsFromSource(V source) {
		BFSDistanceLabeler<V, E> labeler = new BFSDistanceLabeler<V, E>();
		labeler.labelDistances(mGraph, source);
		distances = labeler.getDistanceDecorator();
		Map<V, Number> currentSourceSPMap = new HashMap<V, Number>();
		Map<V, E> currentSourceEdgeMap = new HashMap<V, E>();

		for (V vertex : mGraph.getVertices()) {

			Number distanceVal = distances.get(vertex);
			// BFSDistanceLabeler uses -1 to indicate unreachable vertices;
			// don't bother to store unreachable vertices
			if (distanceVal != null && distanceVal.intValue() >= 0) {
				currentSourceSPMap.put(vertex, distanceVal);
				int minDistance = distanceVal.intValue();
				for (E incomingEdge : mGraph.getInEdges(vertex)) {
					for (V neighbor : mGraph
							.getIncidentVertices(incomingEdge)) {
						if (neighbor.equals(vertex)) {
							continue;
						// V neighbor = mGraph.getOpposite(vertex,
						// incomingEdge);
						}

						Number predDistanceVal = distances.get(neighbor);

						int pred_distance = predDistanceVal.intValue();
						if (pred_distance < minDistance && pred_distance >= 0) {
							minDistance = predDistanceVal.intValue();
							currentSourceEdgeMap.put(vertex, incomingEdge);
						}
					}
				}
			}
		}
		mDistanceMap.put(source, currentSourceSPMap);
		mIncomingEdgeMap.put(source, currentSourceEdgeMap);
	}

	/**
	 * Clears all stored distances for this instance. Should be called whenever
	 * the graph is modified (edge weights changed or edges added/removed). If
	 * the user knows that some currently calculated distances are unaffected by
	 * a change, <code>reset(V)</code> may be appropriate instead.
	 * 
	 * @see #reset(Object)
	 */
	public void reset() {
		mDistanceMap.clear();
		mIncomingEdgeMap.clear();
	}

	/**
	 * Clears all stored distances for the specified source vertex
	 * <code>source</code>. Should be called whenever the stored distances from
	 * this vertex are invalidated by changes to the graph.
	 * 
	 * @see #reset()
	 */
	public void reset(V v) {
		mDistanceMap.remove(v);
		mIncomingEdgeMap.remove(v);
	}
}
